//
//  JKDownLoader.m
//  JKDownLoader
//
//  Created by 王冲 on 2019/2/8.
//  Copyright © 2019年 JK科技有限公司. All rights reserved.
//

#import "JKDownLoader.h"
// 有关沙盒的操作
#import "JKFilePathExtension.h"

// 超时的时间
#define kTimeOut 20.0

static NSString * const downTempPath = @"123";


@interface JKDownLoader ()<NSURLSessionDataDelegate>
{
    // 下载失败的原因
    NSString *errorMessage;
}
// 全局的网络会话，管理所有的网络任务
@property(nonatomic,strong) NSURLSession *session;

/**
 设置一个全局的下载任务
 */
@property(nonatomic,strong) NSURLSessionDataTask *downloadTask;

/**
 保存断开时的data
 */
@property(nonatomic,strong) NSData *resumeData;

/* 保存文件的输出流
 - (void)open; 写入之前，打开流
 - (void)close; 写入完毕之后，关闭流
 */
@property(nonatomic,strong)NSOutputStream *fileStream;

/**
 下载完成的路径
 */
@property (nonatomic, copy) NSString *downLoadedPath;

/**
 正在下载的路径
 */
@property (nonatomic, copy) NSString *downLoadingPath;

@end

@implementation JKDownLoader


-(NSURLSession *)session{
    
    // NSURLSession 默认是在子线程开启任务的
    if (!_session) {
        
        /**
         全局网络环境的一个配置
         比如：身份验证，浏览器类型以及缓存，超时，这些都会被记录在
         */
        
        /**
         主要说一下这个函数参数的意义；
         参数
         
         1. 配置 config
         2. 代理 self
         3. 代理工作的队列 必须是NSOperationQueue队列
         
             [NSOperationQueue mainQueue] 代理在主线程上工作
             nil － 代理异步工作
             [[NSOperationQueue alloc] init] - 代理在异步工作

         注意：URLSession 请求本身的网络操作是异步的，无论指定什么队列，都是在异步执行的
         
             － 这里制定的队列是在此指定的代理的工作队列，表示发生网络事件后，希望在哪一个线程中工作！
         队列的选择类型依据与 NSURLConnection 异步的队列选择依据相同！
         
             －如果完成后需要做复杂(耗时)的处理，可以选择异步队列
         
             －如果完成后直接更新UI，可以选择主队列

         强调：session本身工作的线程，与代理工作的线程是不一样的
         
         */
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
        _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
        // [NSOperationQueue mainQueue]
        //  [[NSOperationQueue alloc] init]
    }
    return _session;
}



#pragma mark 开始下载

-(void)jk_downLoader:(NSURL *)url withDownInfo:(downLoadInfoBlock)downInfo withProgress:(downLoadProgressChange)progress withdownLoadStateChangeType:(downLoadStateChangeTypeBlock)stateType withSuccess:(downLoadSuccessBlock)success withFail:(downLoadFailBlock)fail{
    
    // 保存下载的 URL
    self.downloadUrl = url;
    
    // 1.给所有的blockf赋值
    self.downInfo = downInfo;
    self.downProgress = progress;
    self.stateChange = stateType;
    self.success = success;
    self.fail = fail;
    
    // 2.调用下载
    [self jk_downLoader:url];
}

-(void)jk_downLoader:(NSURL *)url{
    
    /**
       内部实现
       1、真正的从头开始下载
       2、如果任务存在了，继续下载
     */
    
    // 当前任务，肯定存在
    if ([url isEqual:self.downloadTask.originalRequest.URL]) {
        
        // 判断当前的状态，如果是暂停状态,再继续x
        if (self.state == JKDownLoaderStatePause) {
            
            [self jk_resumeCurrentTask];
            // 下面就没有必要判断了
            return;
        }
    }
    
    // 能来到这里有两种情况：1、任务不存在；2、任务存在，但是任务url地址不同
    [self jk_cancelCurrentTask];
    
    
    // 0、判断下载文件夹和下载完成的文件夹是否存在，不存在就创建
    [JKFilePathExtension jk_createFolder:cachesPathLoading];
    [JKFilePathExtension jk_createFolder:cachesPathCompleted];
    
    // 1、检查服务器文件的大小以及其他的信息
    [self selectServerFileInfoWithUrl:url];
    NSLog(@"服务器文件总大小：%lld",self.expectdContentLength);
    
    self.downLoadedPath = [cachesPathCompleted stringByAppendingPathComponent:self.downloadSuggestedFilename];
    self.downLoadingPath = [cachesPathLoading stringByAppendingPathComponent:self.downloadSuggestedFilename];
    
    // 2.去下载完成的文件夹(JKDownloadCompleted)查看是否已经下载
    if ([JKFilePathExtension jk_judgeFileExists:self.downLoadedPath]) {
        
        NSLog(@"存在,说明下载完成");
        // 下载完成
        self.state = JKGDownLoaderStateSuccess;
        return;
    }
    
    // 3.去下载的文件夹查看是否存在,如果存在查看下载了多少
    if (![self checkLocalFileInfo]) {
        NSLog(@"文件已经下载到本地，不需要再次下载");
        self.state = JKGDownLoaderStateSuccess;
        return;
    }
    
    
    // 4.开始去下载
    [self downLoadWithUrl:url];
    
    
}

#pragma mark ----私有方法----

#pragma mark 1.从服务器查询下载文件的信息
// 1.从服务器查询下载文件的信息
-(void)selectServerFileInfoWithUrl:(NSURL *)url{
    
    // 1.请求信息
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:1 timeoutInterval:kTimeOut];
    request.HTTPMethod = @"HEAD";
    
    // 2.建立网络连接
    NSURLResponse *response = nil;
    NSError *error = nil;
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
    if (error) return;
    
    NSLog(@"%@ %lld",response,response.expectedContentLength);
    
    // 总大小传出去
    if (self.downInfo) {
        
        self.downInfo(response.expectedContentLength,self.downLoadedPath);
    }
    
    // 3.记录服务器的文件信息
    // 3.1、文件长度
    self.expectdContentLength = response.expectedContentLength;
    // 3.2、建议保存的文件名字
    self.downloadSuggestedFilename =response.suggestedFilename;
}

#pragma mark 2.从本地检查要下载的文件信息
/**
 2.从本地检查要下载的文件信息(除了文件下载完，其他的情况都需要下载文件)
 
 @return YES：需要下载，NO：不需要下载
 */
-(BOOL)checkLocalFileInfo{
    
    long long fileSize = 0;
    
    NSString *path = [cachesPathLoading stringByAppendingPathComponent:self.downloadSuggestedFilename];
    // 1.判断文件是否存在
    if ([JKFilePathExtension jk_judgeFileExists:path]) {
        
        // 获取本地存在文件大小
        fileSize = [JKFilePathExtension jk_fileSize:path];
        NSLog(@"当前的大小=%lld",fileSize);
    }
    
    // 2.根据文件大小来判断文件是否存在
    if(fileSize > self.expectdContentLength){
        
        // 文件异常，删除该文件(删除临时缓存)
        NSLog(@"删除临时缓存");
        [JKFilePathExtension removeFile:path];
        fileSize = 0;
    }else if (fileSize == self.expectdContentLength)
    {
        // 文件已经下载完,移动路径
        self.state = JKGDownLoaderStateSuccess;
        [JKFilePathExtension moveFile:self.downLoadingPath toPath:self.downLoadedPath];
        return NO;
    }
    
    // 记录下载的位置
    self.currentContentLength = fileSize;
    self.downLoadingPath = path;
    
    return YES;
}

#pragma mark 3.开始下载文件
- (void)downLoadWithUrl:(NSURL *)url {
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:0];
    [request setValue:[NSString stringWithFormat:@"bytes=%lld-", self.currentContentLength] forHTTPHeaderField:@"Range"];
    // session 分配的task, 默认情况, 挂起状态
    self.downloadTask = [self.session dataTaskWithRequest:request];
    // 由于合并状态，统一使用下面的方法
    // [self.downloadTask resume];
    [self jk_resumeCurrentTask];
}

#pragma mark - 协议方法

// 第一次接受到相应的时候调用(响应头, 并没有具体的资源内容)
// 通过这个方法, 里面, 系统提供的回调代码块, 可以控制, 是继续请求, 还是取消本次请求
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSHTTPURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {

    // 在这里也可以进行判断(由于请求的太慢，故挪在前面判断)
    // NSURLSessionResponseCancel 执行后后面的代理方法就不会再走
    
    NSLog(@"downLoadingPath=%@",self.downLoadingPath);
    
    NSLog(@"线程=%@",[NSThread currentThread]);
    
    // 创建输出流
    self.fileStream = [[NSOutputStream alloc]initToFileAtPath:self.downLoadingPath append:YES];
    [self.fileStream open];
    // 继续接受数据
    completionHandler(NSURLSessionResponseAllow);
    
}

// 当用户确定, 继续接受数据的时候调用
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
    // JKLog(@"接收到的数据长度=%tu",data.length);
    self.currentContentLength += data.length;
    
    // 计算百分比
    // progress = (float)long long / long long
    self.progress  = (float)self.currentContentLength/self.expectdContentLength;
    
    // NSLog(@"下载的进度=%f",self.progress);
    
    // NSLog(@"线程=%@",[NSThread currentThread]);
    
    //在主线程更新UI
    dispatch_async(dispatch_get_main_queue(), ^{
        
        // self.progressLabel.text = [NSString stringWithFormat:@"下载进度：%f",progress];
    });
    
    // 将数据拼接起来，并判断是否可写如，一般情况下可写入，除非磁盘空间不足
    //if ([self.fileStream hasSpaceAvailable]) {
        
        [self.fileStream write:data.bytes maxLength:data.length];
    //}
}

// 请求完成的时候调用( != 请求成功/失败)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    NSLog(@"请求完成");
    
    if (error == nil) {
        NSLog(@"下载没问题");
        // 不一定是成功
        // 数据是肯定可以请求完毕
        // 判断, 本地缓存 == 文件总大小 {filename: filesize: md5:xxx}
        // 如果等于 => 验证, 是否文件完整(file md5 )
        
        /**
         下载-文件完整性验证机制：验证文件的合法性, 下载数据是否完整可用
         1. 服务器返回文件下载信息的同时, 会返回该文件内容的md5值
         2. 本地下载完成后, 可以, 在本地已下载的文件的MD5值和服务器返回的进行对比;
         3.为了简单, 有的, 服务器返回的下载文件MD5值, 直接就是命名称为对应的文件名称
         */
        
        // 文件已经下载完,移动路径（前提是上面的没问题）
        [JKFilePathExtension moveFile:self.downLoadingPath toPath:self.downLoadedPath];
        
        self.state = JKGDownLoaderStateSuccess;

    }else{
        
        NSLog(@"下载有问题：%zd 详情：%@",error.code,error.localizedDescription);
        
        if (error.code == -999) {
            
            self.state = JKDownLoaderStatePause;
        }else{
            
            // 失败的原因
            errorMessage = [NSString stringWithFormat:@"%@",error.localizedDescription];
            self.state = JKGDownLoaderStateFailed;
        }

    }
    
    // 关闭流
    [self.fileStream close];
}


#pragma mark 暂停当前的下载任务
/**
 暂停当前的下载任务
 
 解释：如果调用了几次继续，那就需要调用几次暂停才可以暂停
 解决办法：引入状态
 */
-(void)jk_pauseCurrentTask{
    
    // 正在下载才可以暂停
    if (self.state == JKGDownLoaderStateDowning) {
       
        self.state = JKDownLoaderStatePause;
        [self.downloadTask suspend];
    }
}

#pragma mark 取消当前的下载任务
/**
 取消当前的下载任务
 */
-(void)jk_cancelCurrentTask{
    
    self.state = JKDownLoaderStatePause;
    
    [self.session invalidateAndCancel];
    // 懒加载开启的，那么久赋值为nil，下载开启下载任务重新创建
    self.session = nil;
    
}
#pragma mark 取消并清理当前的下载任务
/**
 取消当前的下载任务, 并清理资源(这里指的是所有的下载任务)
 */
-(void)jk_cancelAndCleanCurrentTask{
    
    // 下载完成的文件 -> 手动删除某个声音 -> 统一清理缓存
    [self jk_cancelCurrentTask];
    [JKFilePathExtension removeFile:self.downLoadingPath];
    
}

#pragma mark 继续当前的下载任务
/**
  继续当前的下载任务
 
  解释：如果调用了几次暂停，就要调用几次继续，才可以就继续
  解决办法：引入状态
 */
-(void)jk_resumeCurrentTask{
    
    NSLog(@"state=%lu",(unsigned long)self.state);
    
    // 正在暂停状态才可以继续下载(加上self.downloadTask是防止第一次，默认的是暂停状态，是无法继续的，因为没有开启任务)
    if (self.downloadTask && self.state == JKDownLoaderStatePause) {
        
        [self.downloadTask resume];
        // 状态改为正在下载
        self.state = JKGDownLoaderStateDowning;
    }
}


#pragma mark state 的状态过滤
-(void)setState:(JKDownLoaderState)state{
    
    if (_state == state) {
        return;
    }
    _state = state;
    
    // 代理、block、通知
    if (self.stateChange) {
        
        self.stateChange(_state);
    }
    
    // 成功后传出成功的路径
    if (_state == JKGDownLoaderStateSuccess && self.success) {
        
        self.success(self.downLoadedPath);
    }
    
    // 下载失败后的回调
    if (_state == JKGDownLoaderStateFailed && self.fail) {
        
        self.fail(errorMessage);
    }
    
    
    NSLog(@"downloadUrl=%@",self.downloadUrl);
    
    [[NSNotificationCenter defaultCenter] postNotificationName:JKDownLoadURLOrStateChangeNotification object:nil userInfo:@{@"downLoadURL": self.downloadUrl,@"downLoadState": @(self.state)}];
}

-(void)setProgress:(float)progress{
    
    _progress = progress;
    
    if (self.downProgress) {
        self.downProgress(_progress);
    }
}

#pragma mark 获取下载完成的文件路径
+ (NSString *)downLoadedFileWithURL: (NSURL *)url {
    
    NSString *cacheFilePath = [cachesPathCompleted stringByAppendingPathComponent:url.lastPathComponent];
    
    if([JKFilePathExtension jk_judgeFileExists:cacheFilePath]) {
        return cacheFilePath;
    }
    return nil;
    
}

@end
